package cn.tedu.mall.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.pojo.domain.CsmallAuthenticationInfo;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.order.service.IOmsOrderService;
import cn.tedu.mall.pojo.order.dto.OrderAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderItemAddDTO;
import cn.tedu.mall.pojo.order.vo.OrderAddVO;
import cn.tedu.mall.pojo.seckill.dto.SeckillOrderAddDTO;
import cn.tedu.mall.pojo.seckill.model.Success;
import cn.tedu.mall.pojo.seckill.vo.SeckillCommitVO;
import cn.tedu.mall.seckill.config.RabbitMqComponentConfiguration;
import cn.tedu.mall.seckill.service.ISeckillService;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class SeckillServiceImpl implements ISeckillService {
    //  普通订单生成的方法,dubbo调用
    @DubboReference
    private IOmsOrderService dubboOrderService;
    // 减少Redis库存是操作redis字符串尅醒的数据
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    // 秒杀订单提交成功,需要发送到消息队列后需处理
    @Autowired
    private RabbitTemplate rabbitTemplate;



    @Override
    public SeckillCommitVO commitSeckill(SeckillOrderAddDTO seckillOrderAddDTO) {
            // 一 .从redis中检查 库存和是否重复购买
            //  防止超卖,redis中预热了sku的库存,判断用户请求的skuId是否有库存,有可以购买,没有阻止购买
        Long skuId = seckillOrderAddDTO.getSeckillOrderItemAddDTO().getSkuId();
        // 获得秒杀用户的id
        Long userId = getUserId();
        // 获得skuId和userId就能明确知道谁买了什么商品
        // 秒杀业务规定一个用户只能购买sku一次
        //   该用户和sku的购买关系保存到redis中防止再次购买
        //   生成用户和此sku关联的key
        String reSeckillKey = SeckillCacheUtils.getReseckillCheckKey(skuId,userId);
        // 向这个key中保存一个数据(一般保存一个1代表购买一次)可以使用自增的方式
        //   increment() 方法
        //   1. 如果key为空 会自动将这个值保存为空
        //  2. 如果key不为空 会自增当前的值  1->2  2->3  3->4
        //  最后将这个数值返回给seckillTimes
        Long seckillTimes = stringRedisTemplate.boundValueOps(reSeckillKey).increment();
        if(seckillTimes > 1){
            // 购买次数超过1 , 不是第一次购买 抛出异常终止业务
            throw new CoolSharkServiceException(ResponseCode.FORBIDDEN,"您已经购买过该商品");
        }
        // 运行到此处证明用户符合购买资格(之前没买过)
        // 检查是否还有库存,先确定 库存的key
        String seckillSkuCountKey = SeckillCacheUtils.getStockKey(skuId);
        //   从redis中获得库存
        //  调用decreament()方法,将库存数减一,返回的数值,就是减一后的库存数
        Long leftStock = stringRedisTemplate.boundValueOps(seckillSkuCountKey).decrement(1);
        //  leftStock 表示当前用户购买完之后剩余的 库存数
        // 如果是0 表示此用户购买完库存为0,所以只有返回小于0时才是没有库存了
        if (leftStock <0){
            throw new CoolSharkServiceException(ResponseCode.NOT_ACCEPTABLE,"您购买的商品已经无货了");
        }
        //   运行到此处,表示用户可以生成订单,进入第二阶段
        //  二. 开始生成订单秒杀订单转换成普通订单
        //   获得到的秒杀订单参数seckillOrderAddDTO
        // 这个参数信息是由前端收集并发送到后端的,他的信息和普通订单发送的内容基本相同
        //  可以直接调用dubbo调用Order模块的新增订单业务来完成
        //  通过调用转换方法将seckillOrderAddDTO转换为OrderAddDTO类型对象
        OrderAddDTO orderAddDTO = convertSeckillOrderToOrder(seckillOrderAddDTO);
        // 转换过程中没有UserID需要手动赋值
        orderAddDTO.setUserId(userId);
        // dubbo调用 Order模块新增订单
        OrderAddVO orderAddVO = dubboOrderService.addOrder(orderAddDTO);
        //  订单生成完毕
        //  三. 消息队列发送消息
        // 将购买成功的信息保存到success表中
        //  该业务不需要立即完成,可以将它们发送给消息队列,异步完成,减轻当前业务的运行压力
        Success success = new Success();
        //  success主要保存成功的信息,或者描述为秒杀卖出的商品
        // 具体到商品是sku信息,所以它的属性和seckillOrderAddDTO更相似
        //
        BeanUtils.copyProperties(seckillOrderAddDTO.getSeckillOrderItemAddDTO(),success);
        // 缺少的属性赋值
        success.setUserId(userId);
        success.setOrderSn(orderAddVO.getSn());
        //  success收集完成,将消息发送给RabbitMQ
        rabbitTemplate.convertAndSend(RabbitMqComponentConfiguration.SECKILL_EX,
                RabbitMqComponentConfiguration.SECKILL_RK,success);
        //  消息队列处理后续的 操作
        // SeckillCommitVO 和 OrderAddVO属性一致,实例化后赋值返回
        SeckillCommitVO seckillCommitVO = new SeckillCommitVO();
        BeanUtils.copyProperties(orderAddVO,seckillCommitVO);
        return seckillCommitVO;
    }

    // 将秒杀订单转换为普通订单的方法
    private OrderAddDTO convertSeckillOrderToOrder(SeckillOrderAddDTO seckillOrderAddDTO) {
        // 实例化OrderAddDTO
        OrderAddDTO orderAddDTO = new OrderAddDTO();
        BeanUtils.copyProperties(seckillOrderAddDTO,orderAddDTO);
        //  seckillOrderAddDTO 秒杀订单中只可能对应一个OrderItem对象
        // 普通 订单中包含多个OrderItem对象
        OrderItemAddDTO orderItemAddDTO = new OrderItemAddDTO();
        // 将seckillOrderAddDTO同名属性赋值给orderItemAddDTO
        BeanUtils.copyProperties(seckillOrderAddDTO.getSeckillOrderItemAddDTO(),orderItemAddDTO);
        // 将赋好值的orderItemAddDTO对象添加到orderAddDTO的集合中
        List<OrderItemAddDTO> orderItemAddDTOs = new ArrayList<>();
        orderItemAddDTOs.add(orderItemAddDTO);
        // 将集合赋值到orderAddDTO对象中
        orderAddDTO.setOrderItems(orderItemAddDTOs);

        // 返回转换完成的orderDTO对象
        return orderAddDTO;
    }

    public CsmallAuthenticationInfo getUserInfo(){
        // 获得SpringSecurity容器对象
        UsernamePasswordAuthenticationToken authenticationToken=
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.
                        getContext().getAuthentication();
        // 判断获取的容器信息是否为空
        if(authenticationToken!=null){
            // 如果容器中有内容,证明当前容器中有登录用户信息
            // 我们获取这个用户信息并返回
            CsmallAuthenticationInfo csmallAuthenticationInfo=
                    (CsmallAuthenticationInfo)authenticationToken.getCredentials();
            return csmallAuthenticationInfo;
        }
        throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,"没有登录信息");
    }
    // 业务逻辑层中大多数方法都是获得用户id,所以编写一个返回用户id的方法
    public Long getUserId(){
        return getUserInfo().getId();
    }
}
